﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using Microsoft.SharePoint;

namespace Core.Foundation.Standards
{    
    public class AppConfig
    {
        public class Attributes
        {
            public interface IAttribute<T>
            {
                T Value { get; }
            }
            public interface IAttributeGuid
            {
                Guid SiteColumnID { get; }
            }


            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigModuleAttribute : Attribute, IAttribute<string>, IAttributeGuid
            {
                private readonly string value;

                public AppConfigModuleAttribute(string value)
                {
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Module; }
                }
            }

            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigGroupAttribute : Attribute, IAttribute<string>, IAttributeGuid
            {
                private readonly string value;

                public AppConfigGroupAttribute(string value)
                {
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Group; }
                }
            }


            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigKeyAttribute : Attribute, IAttribute<string>, IAttributeGuid
            {
                private readonly string value;

                public AppConfigKeyAttribute(string value)
                {
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Key ; }
                }
            }

            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigValueAttribute : Attribute, IAttribute<string>, IAttributeGuid
            {
                private readonly string value;

                public AppConfigValueAttribute(string value)
                {
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Value ; }
                }
            }

            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigActiveAttribute : Attribute, IAttribute<bool>, IAttributeGuid
            {
                private readonly bool value;

                public AppConfigActiveAttribute(bool value)
                {
                    this.value = value;
                }

                public bool Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Active ; }
                }
            }

            [AttributeUsage(AttributeTargets.Field)]
            public sealed class AppConfigEncryptedAttribute : Attribute, IAttribute<bool>, IAttributeGuid
            {
                private readonly bool value;

                public AppConfigEncryptedAttribute(bool value)
                {
                    this.value = value;
                }

                public bool Value
                {
                    get { return value; }
                }

                public Guid SiteColumnID
                {
                    get { return FoundationColumnID.Encrypted; }
                }
            }  


            public static R GetAttributeValue<T, R>(Enum @enum)
            {
                R attributeValue = default(R);

                if (@enum != null)
                {
                    FieldInfo fi = @enum.GetType().GetField(@enum.ToString());

                    if (fi != null)
                    {

                        T[] attributes = fi.GetCustomAttributes(typeof(T), false) as T[];

                        if (attributes != null && attributes.Length > 0)
                        {

                            IAttribute<R> attribute = attributes[0] as IAttribute<R>;
                            if (attribute != null)
                            {
                                attributeValue = attribute.Value;
                            }
                        }
                    }
                }

                return attributeValue;
            }
            

            public static Guid GetAttributeSiteColumn<T>(Enum @enum)
            {
                Guid siteColumnID = default(Guid);

                if (@enum != null)
                {
                    FieldInfo fi = @enum.GetType().GetField(@enum.ToString());

                    if (fi != null)
                    {

                        T[] attributes = fi.GetCustomAttributes(typeof(T), false) as T[];

                        if (attributes != null && attributes.Length > 0)
                        {

                            IAttributeGuid attribute = attributes[0] as IAttributeGuid;
                            if (attribute != null)
                            {
                                siteColumnID = attribute.SiteColumnID;
                            }
                        }
                    }
                }

                return siteColumnID;
            }
            
            

        }

        static public class Helper
        {

            public static bool IsAppConfigActive(Enum configItem)
            {
                Version solution = default(Version);
                return IsAppConfigActive(configItem, solution);                
            }

            public static bool IsAppConfigActive(Enum configItem, Version solution)
            {
                // ignore encrypted flag
                bool isEncrypted = false;
                bool isActive = false;
                GetAppConfig(configItem, out isActive, out isEncrypted, solution);
                return isActive;
            }


            public static string GetAppConfig(Enum configItem)
            {
                Version solution = default(Version);
                return GetAppConfig(configItem, solution);
            }

            public static string GetAppConfig(Enum configItem, Version solution)
            {               
                // ignore encrypted flag
                bool isEncrypted = false;
                bool isActive = false;
                return GetAppConfig(configItem, out isActive, out isEncrypted, solution);
            }

            /// <summary>
            /// GetAppConfig
            /// </summary>
            /// <param name="configItem"></param>
            /// <param name="isEncypted"></param>
            /// <param name="solution"></param>
            /// <returns></returns>
            public static string GetAppConfig(Enum configItem, out bool isActive, out bool isEncrypted, Version solution)
            {
                string appConfigModule = AppConfig.Attributes.GetAttributeValue<AppConfig.Attributes.AppConfigModuleAttribute, string>(configItem);
                string appConfigGroup = AppConfig.Attributes.GetAttributeValue<AppConfig.Attributes.AppConfigGroupAttribute, string>(configItem);
                string appConfigKey = AppConfig.Attributes.GetAttributeValue<AppConfig.Attributes.AppConfigKeyAttribute, string>(configItem);

                string result = string.Empty;                
                if (solution == default(Version))
                    result = ReadAppConfigItem(appConfigModule, appConfigGroup, appConfigKey, out isActive, out isEncrypted);
                else
                    result = ReadAppConfigItem(appConfigModule, appConfigGroup, appConfigKey, out isActive, out isEncrypted, solution.ToString());
                
                return result;
            }

            /// <summary>
            /// AddSolutionVersion
            /// </summary>
            /// <param name="moduleName"></param>
            public static void AddSolutionVersion(string moduleName)
            {                
                Assembly thisAssembly = Assembly.GetCallingAssembly();
                FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(thisAssembly.Location);
                string solutionVersion = fvi.FileVersion.ToString();
                                
                AddAppConfigItem(moduleName,
                                 FoundationConstants.AppConfigVersioningGroup,
                                 FoundationConstants.AppConfigVersioningSolutionKey,
                                 solutionVersion,
                                 solutionVersion,
                                 true,
                                 false);
            }
            
            public static string GetSolutionVersion(string moduleName)
            {
                bool isActive = false;
                bool isEncrypted = false;
                return ReadAppConfigItem(moduleName,
                                         FoundationConstants.AppConfigVersioningGroup,
                                         FoundationConstants.AppConfigVersioningSolutionKey,
                                         out isActive, 
                                         out isEncrypted);                
            }

            /// <summary>
            /// DO NOT USE IN PRODUCTION - Removes all items in the Module, Group, Key match.
            /// </summary>
            /// <param name="web"></param>
            /// <param name="acModule"></param>
            /// <param name="acGroup"></param>
            /// <param name="acKey"></param>            
            public static void RemoveAppConfigItems(string acModule, string acGroup, string acKey, string acVersion)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    SPWeb web = CoreHelper.GetCoreWeb();
                    Guid webSafe = CoreHelper.AllowUnSafeUpdates(ref web);
                    try
                    {
                        // Get our list
                        SPList list = web.Lists[FoundationConstants.AppConfigListName];
                        // Setup our query 
                        SPQuery itemsQuery = new SPQuery();
                        itemsQuery.Query = CAMLQueries.GetVersionedAppConfigItems(acModule, acGroup, acKey, acVersion);
                        // Get Items that match
                        SPListItemCollection listItems = list.GetItems(itemsQuery);

                        // ensure we have items to delete
                        if (listItems.Count > 0)
                        {
                            // build batch xml to delete all items at once - save performance and avoid list deletion iteration issues.
                            StringBuilder sbDelete = new StringBuilder();
                            sbDelete.Append(@"<?xml version=""1.0"" encoding=""UTF-8""?><Batch OnError=""Return"">");
                            // build each item deletion command
                            for (int index = 0; index < listItems.Count; index++)
                            {
                                sbDelete.AppendFormat(@"<Method ID=""{0}""><SetList Scope=""Request"">{1}</SetList><SetVar Name=""Cmd"">DELETE</SetVar><SetVar Name=""ID"">{2}</SetVar></Method>",
                                                      index,
                                                      list.ID,
                                                      listItems[index].ID
                                                      );
                            }
                            // close the batch
                            sbDelete.Append(@"</Batch>");
                            // execute
                            string batchReturn = web.ProcessBatchData(sbDelete.ToString());
                        }
                    }
                    catch (Exception ex)
                    {
                        Logging.LogError(ex, FoundationConstants.standardsLog);
                    }
                    CoreHelper.AllowUnSafeUpdates(ref web, webSafe);
                    web.Dispose();
                });
            }


            public static void AppAppConfigItem(string acModule, string acGroup, string acKey, string acValue, string solutionVersion)
            {
                bool acActive = true;
                bool acEncrypted = false;
                AddAppConfigItem(acModule, acGroup, acKey, acValue, solutionVersion, acActive, acEncrypted);
            }

            /// <summary>
            /// Adds a single item specified to the AppConfig list
            /// </summary>
            /// <param name="web"></param>
            /// <param name="acModule"></param>
            /// <param name="acGroup"></param>
            /// <param name="acKey"></param>
            /// <param name="acValue"></param>
            /// <param name="acActive"></param>
            public static void AddAppConfigItem(string acModule, string acGroup, string acKey, string acValue, string solutionVersion, bool acActive = true, bool acEncrypted = false)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    SPWeb web = CoreHelper.GetCoreWeb();
                    try
                    {
                        // Get our list
                        SPList list = web.Lists[FoundationConstants.AppConfigListName];
                        // Setup our query 
                        SPQuery itemsQuery = new SPQuery();
                        itemsQuery.Query = CAMLQueries.GetAppConfigItems(acModule, acGroup, acKey, acValue);
                        // Get Items that match
                        SPListItemCollection listItems = list.GetItems(itemsQuery);

                        // should we add?
                        if (listItems.Count == 0)
                        {
                            Guid webSafe = CoreHelper.AllowUnSafeUpdates(ref web);
                            SPListItem listItem = list.AddItem();
                            // setup Guid
                            Guid attributeGuid = default(Guid);
                            // Module Column
                            attributeGuid = FoundationColumnID.Module;
                            listItem[attributeGuid] = acModule;
                            // Group Column
                            attributeGuid = FoundationColumnID.Group;
                            listItem[attributeGuid] = acGroup;
                            // Key Column
                            attributeGuid = FoundationColumnID.Key;
                            listItem[attributeGuid] = acKey;
                            // Value Column
                            attributeGuid = FoundationColumnID.Value;
                            listItem[attributeGuid] = acValue;
                            // Version Column
                            attributeGuid = FoundationColumnID.Version;
                            listItem[attributeGuid] = solutionVersion;
                            // Active Column
                            attributeGuid = FoundationColumnID.Active;
                            listItem[attributeGuid] = acActive;
                            // Encrypted Column
                            attributeGuid = FoundationColumnID.Encrypted;
                            listItem[attributeGuid] = acEncrypted;
                            // update listitem and list
                            listItem.Update();
                            list.Update();
                            // return web to normal
                            CoreHelper.AllowUnSafeUpdates(ref web, webSafe);
                        }

                    }
                    catch (Exception ex)
                    {
                        Logging.LogError(ex, FoundationConstants.standardsLog);
                    }
                    finally
                    {
                        web.Dispose();
                    }
                });
            }


            public static string ReadAppConfigItem(string acModule, string acGroup, string acKey, out bool isActive, out bool isEncrypted)
            {
                string acVersion = default(string);
                return ReadAppConfigItem(acModule, acGroup, acKey, out isActive, out isEncrypted, acVersion);
            }

            public static string ReadAppConfigItem(string acModule, string acGroup, string acKey, out bool isActive, out bool isEncrypted, string acVersion)
            {
                bool _isActive = true;
                bool _isEncrypted = false;
                string returnValue = string.Empty;                    
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    SPWeb web = CoreHelper.GetCoreWeb();                    
                    try
                    {
                        // Get our list
                        SPList list = web.Lists[FoundationConstants.AppConfigListName];

                        if (acVersion == default(string))
                        {
                            // Setup our query 
                            SPQuery itemsQuery = new SPQuery();
                            itemsQuery.Query = CAMLQueries.GetAppConfigItems(acModule, acGroup, acKey);
                            // Get Items that match
                            SPListItemCollection allItems = list.GetItems(itemsQuery);
                            acVersion = GetLatestAppConfigVersion(allItems);
                        }

                        // Setup our query 
                        SPQuery itemQuery = new SPQuery();
                        itemQuery.Query = CAMLQueries.GetVersionedAppConfigItems(acModule, acGroup, acKey, acVersion);
                        // Get Items that match
                        SPListItemCollection specificVersionItems = list.GetItems(itemQuery);
                        // get lastest version if non-specified
                        if (specificVersionItems.Count == 1)
                        {
                            SPListItem listItem = specificVersionItems[0];
                            returnValue = listItem[FoundationColumnID.Value].ToSafeString();
                            _isActive = bool.Parse(listItem[FoundationColumnID.Active].ToString());
                            _isEncrypted = bool.Parse(listItem[FoundationColumnID.Encrypted].ToString());
                        }
                        else
                        {
                            throw new OverflowException("Count Greater Than One");
                        }

                    }
                    catch (Exception ex)
                    {
                        Logging.LogError(ex, FoundationConstants.standardsLog);
                    }
                    finally
                    {
                        web.Dispose();
                    }                    
                });

                isActive = _isActive;
                isEncrypted = _isEncrypted;
                return returnValue;

            }


            /// <summary>
            /// Returns the latest version given a List Collection
            /// </summary>
            /// <param name="filteredAppConfigList"></param>
            /// <returns></returns>
            private static string GetLatestAppConfigVersion(SPListItemCollection filteredAppConfigList)
            {
                Version latestVersion = new Version("0.0.0.0");
                try
                {
                    if (filteredAppConfigList.Count > 1)
                    {
                        foreach (SPListItem item in filteredAppConfigList)
                        {                            
                            string versionString = item[FoundationColumnID.Version].ToString();
                            Version version = new Version(versionString);
                            int compareResult = version.CompareTo(latestVersion);

                            if (compareResult >= 0) // version is newer than latestVersion
                                latestVersion = version;                            
                        }
                    }
                    else
                    {                        
                        latestVersion = new Version(filteredAppConfigList[0][FoundationColumnID.Version].ToString());
                    }

                    if (latestVersion == new Version("0.0.0.0"))
                        throw new Exception("Could not locate am active current version");
                }
                catch (Exception ex)
                {
                    Logging.LogError(ex, FoundationAppConfig.LoggingCategories.Core);
                }
                
                return latestVersion.ToString();                
            }


            public static void AppConfigEntries(object myClass, string solutionVersion)
            {
                bool AddEntries = true;
                AppConfigEntries(myClass, solutionVersion, AddEntries);
            }


            /// <summary>
            /// Handles adding and removing of an AppConfig Enum
            /// </summary>
            /// <param name="myClass"></param>
            /// <param name="web"></param>
            /// <param name="AddEntries"></param>
            public static void AppConfigEntries(object myClass, string solutionVersion, bool AddEntries)
            {                
                try
                {                                        
                    // set our AppConfig class and get the Nested types within
                    Type[] types = myClass.GetType().GetNestedTypes();
                    // loop over each item in the class 
                    foreach (Type type in types)
                    {
                        // check if we have an enum (should only be enums in AppConfig class)
                        if (type.IsEnum)
                        {
                            // are we trying to add or remove entries?
                            if (AddEntries)
                            {
                                #region AddingEntries
                                // loop over each Enum in the class
                                foreach (Enum item in Enum.GetValues(type))
                                {
                                    Type itemType = item.GetType();
                                    MemberInfo[] itemMembers = itemType.GetMembers();                                                                
                                    // loop over every item in the particular Enum
                                    for (int i = 0; i < itemMembers.Length; i++)
                                    {
                                        // ensure we are only getting the enum item members, not built-in items to enums   
                                        if (itemMembers[i].Name == item.ToString())
                                        {
                                            // get all custom attributes from the particular Enum item
                                            Object[] itemAttributes = itemMembers[i].GetCustomAttributes(false);
                                            if (itemAttributes.Length > 0)
                                            {
                                                // does our item already exist
                                                string acModule = string.Empty;
                                                string acGroup = string.Empty;
                                                string acKey = string.Empty;  
                                                string acValue = string.Empty;                                                
                                                bool acActive = false;
                                                bool acEncypted = false;
                                                for (int j = 0; j < itemAttributes.Length; j++)
                                                {
                                                    Attributes.IAttributeGuid attribeGuid = itemAttributes[j] as Attributes.IAttributeGuid;
                                                    if (attribeGuid != null)
                                                    {
                                                        Attributes.IAttribute<string> attribString = itemAttributes[j] as Attributes.IAttribute<string>;
                                                        if (attribString != null)
                                                        {
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Module)
                                                            {
                                                                acModule = attribString.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Group)
                                                            {
                                                                acGroup = attribString.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Key)
                                                            {
                                                                acKey = attribString.Value;
                                                            }                                                            
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Value)
                                                            {
                                                                acValue = attribString.Value;
                                                            } 
                                                        }

                                                        Attributes.IAttribute<bool> attribBool = itemAttributes[j] as Attributes.IAttribute<bool>;
                                                        if (attribBool != null)
                                                        {
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Active)
                                                            {
                                                                acActive = attribBool.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Encrypted)
                                                            {
                                                                acEncypted = attribBool.Value;
                                                            } 
                                                        }
                                                    }
                                                }
                                                
                                                // add already has the logic we want to use to ensure no double adding of same key/value pairs
                                                AddAppConfigItem(acModule, acGroup, acKey, acValue, solutionVersion, acActive, acEncypted);                                                    
                                            }
                                        }
                                    }
                                }
                                #endregion
                            }                                
                            else
                            {
                                #region Remove Entries

                                bool removedItems = false;
                                string acModule = string.Empty;
                                string acGroup = string.Empty;
                                string acKey = string.Empty;
                                string acVersion = string.Empty;

                                // loop over each Enum in the class
                                foreach (Enum item in Enum.GetValues(type))
                                {
                                    Type itemType = item.GetType();
                                    MemberInfo[] itemMembers = itemType.GetMembers();                                                                

                                    // loop over every item in the particular Enum
                                    for (int i = 0; i < itemMembers.Length; i++)
                                    {
                                        // ensure we are only getting the enum item members, not built-in items to enums   
                                        if (itemMembers[i].Name == item.ToString())
                                        {
                                            // get all custom attributes from the particular Enum item
                                            Object[] itemAttributes = itemMembers[i].GetCustomAttributes(false);
                                            if (itemAttributes.Length > 0)
                                            {
                                                for (int j = 0; j < itemAttributes.Length; j++)
                                                {
                                                    Attributes.IAttributeGuid attribeGuid = itemAttributes[j] as Attributes.IAttributeGuid;
                                                    if (attribeGuid != null)
                                                    {
                                                        Attributes.IAttribute<string> attribString = itemAttributes[j] as Attributes.IAttribute<string>;
                                                        if (attribString != null)
                                                        {
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Module)
                                                            {
                                                                acModule = attribString.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Group)
                                                            {
                                                                acGroup = attribString.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Key)
                                                            {
                                                                acKey = attribString.Value;
                                                            }
                                                            if (attribeGuid.SiteColumnID == FoundationColumnID.Version)
                                                            {
                                                                acVersion = attribString.Value;
                                                            }
                                                        }
                                                    }
                                                }
                                                // now remove all items
                                                RemoveAppConfigItems(acModule, acGroup, acKey, acVersion);
                                                removedItems = true;
                                                break;                                                
                                            }
                                        }
                                    }
                                    if (removedItems)
                                    {
                                        break;
                                    }                                    
                                }
                                #endregion                                
                            }
                        }
                    }                  
                }
                catch (Exception ex)
                {
                    Logging.LogError(ex, FoundationConstants.standardsLog);
                }
            }

            
        }
    }
    
}
